package com.wao.editor.service;

import com.wao.editor.client.FileClient;
import com.wao.editor.client.InMemoryMultipartFile;
import com.wao.editor.domain.*;
import com.wao.editor.util.PathFormat;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;

@Component
public class FileManager {

    public static final int BUFFER_SIZE = 8192;
    private final FileClient fileClient;
    @Value("${wao.ueditor.base-url:}")
    public String baseUrl;
    @Value("${wao.ueditor.upload-dir-prefix:ueditor/file/}")
    public String uploadDirPrefix;

    private List<String> domains;

    public FileManager(FileClient fileClient) {
        this.fileClient = fileClient;
    }

    @PostConstruct
    public void afterPropertiesSet() {
        this.domains = fileClient.getDomains();
    }

    public String getBaseUrl() {
        if (StringUtils.isNotBlank(this.baseUrl))
            return baseUrl;
//        if (domains != null && !domains.isEmpty())
//            return domains.stream().filter(e -> e != null).findAny().get();
        return "";
    }

    public List<String> getDomains() {
        return domains;
    }

    private static String getFileName(String fileName) {
        String suffix = FileType.getSuffixByFilename(fileName);
        return new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + (int) (Math.random() * 9000 + 1000) + suffix;
    }

    private static File getTmpFile() {
        File tmpDir = FileUtils.getTempDirectory();
        String tmpFileName = (Math.random() * 10000 + "").replace(".", "");
        return new File(tmpDir, tmpFileName);
    }

    public FileListing listFiles(String uploadDirPrefix, String marker, int limit) {
        try {
            return fileClient.getFileList(uploadDirPrefix, marker, limit, "/");
        } catch (Exception e) {
            return new FileListing();
        }
    }

    public State listFile(int index, String marker, int limit) {
        try {
            FileListing fileListing = fileClient.getFileList(uploadDirPrefix, marker, limit, "/");
            if (fileListing == null)
                return new BaseState(false, AppInfo.NOT_EXIST);
            List<String> fileList = new ArrayList<String>();
            for (FileInfo fileInfo : fileListing.items) {
                fileList.add(fileInfo.key);
            }
            State state = this.getState(fileList.toArray(new String[fileList.size()]));
            state.putInfo("start", index);
            state.putInfo("isLast", fileListing.isEOF() + "");
            state.putInfo("marker", fileListing.marker);
            state.putInfo("total", Integer.MAX_VALUE);
            return state;
        } catch (Exception e) {
            return new BaseState(false, AppInfo.NOT_EXIST);
        }
    }

    public State capture(String[] list, Map<String, Object> conf) {

        MultiState state = new MultiState(true);

        for (String source : list) {
            state.addState(captureRemoteData(source, conf));
        }

        return state;

    }

    public State captureRemoteData(String urlStr, Map<String, Object> conf) {

        HttpURLConnection connection = null;
        URL url = null;
        String suffix = null;
        try {
            url = new URL(urlStr);
            List<String> filters = Arrays.asList((String[]) conf.get("filter"));
            if (filters.contains(url.getHost()))
                return new BaseState(false, AppInfo.PREVENT_HOST);
            connection = (HttpURLConnection) url.openConnection();
            connection.setInstanceFollowRedirects(true);
            connection.setUseCaches(true);
            if (!(HttpURLConnection.HTTP_OK == connection.getResponseCode()))
                return new BaseState(false, AppInfo.CONNECTION_ERROR);
            suffix = MIMEType.getSuffix(connection.getContentType());
            List<String> allowTypes = Arrays.asList((String[]) conf.get("allowFiles"));
            if (!allowTypes.contains(suffix))
                return new BaseState(false, AppInfo.NOT_ALLOW_FILE_TYPE);
            Long maxSize = (Long) conf.get("maxSize");
            if (connection.getContentLength() > maxSize)
                return new BaseState(false, AppInfo.MAX_SIZE);
            State state = saveFileByInputStream(connection.getInputStream(), urlStr);
            if (state.isSuccess())
                state.putInfo("source", urlStr);
            return state;
        } catch (Exception e) {
            return new BaseState(false, AppInfo.REMOTE_FAIL);
        }

    }

    public State upload(HttpServletRequest request, Map<String, Object> conf) {
        if ("true".equals(conf.get("isBase64"))) {
            return base64Uploade(request, conf);
        }
        return binaryUploade(request, conf);
    }

    public State base64Uploade(HttpServletRequest request, Map<String, Object> conf) {
        String filedName = (String) conf.get("fieldName");
        String fileName = request.getParameter(filedName);
        byte[] data = Base64.decodeBase64(fileName);
        long maxSize = ((Long) conf.get("maxSize")).longValue();
        if (data.length > maxSize)
            return new BaseState(false, AppInfo.MAX_SIZE);
        String suffix = FileType.getSuffix("JPG");
        String savePath = PathFormat.parse((String) conf.get("savePath"), (String) conf.get("filename"));
        savePath = savePath + suffix;
        String rootPath = "";
        String physicalPath = rootPath + savePath;
        State storageState = saveBinaryFile(data, physicalPath);
        if (storageState.isSuccess()) {
            storageState.putInfo("url", PathFormat.format(savePath));
            storageState.putInfo("type", suffix);
            storageState.putInfo("original", "");
        }
        return storageState;
    }

    public State binaryUploade(HttpServletRequest request, Map<String, Object> conf) {
        if (!ServletFileUpload.isMultipartContent(request))
            return new BaseState(false, AppInfo.NOT_MULTIPART_CONTENT);
        MultipartFile file = ((MultipartHttpServletRequest) request).getFile("upfile");
        String suffix = FileType.getSuffixByFilename(file.getOriginalFilename());
        long maxSize = ((Long) conf.get("maxSize")).longValue();
        List<String> allowTypes = Arrays.asList((String[]) conf.get("allowFiles"));
        if (!allowTypes.contains(suffix))
            return new BaseState(false, AppInfo.NOT_ALLOW_FILE_TYPE);
        try {
            State storageState = saveFileByInputStream(file.getInputStream(), file.getOriginalFilename(), maxSize, uploadDirPrefix);
            if (storageState.isSuccess()) {
                storageState.putInfo("type", suffix);
                storageState.putInfo("original", file.getOriginalFilename());
                storageState.putInfo("originalName", file.getOriginalFilename());
            }
            return storageState;
        } catch (IOException e) {
            return new BaseState(false, AppInfo.IO_ERROR);
        }
    }

    private State saveBinaryFile(byte[] data, String path) {
        return saveFileByInputStream(new ByteArrayInputStream(data), path);
    }

    public State saveFileByInputStream(InputStream is, String path) {
        return saveFileByInputStream(is, path, null, uploadDirPrefix);
    }

    public State saveFileByInputStream(InputStream is, String path, Long maxSize, String uploadDirPrefix) {
        File tmpFile = getTmpFile();
        byte[] dataBuf = new byte[2048];
        BufferedInputStream bis = new BufferedInputStream(is, BUFFER_SIZE);
        try {
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tmpFile), BUFFER_SIZE);
            int count = 0;
            while ((count = bis.read(dataBuf)) != -1) {
                bos.write(dataBuf, 0, count);
            }
            bos.flush();
            bos.close();
            if (maxSize != null && tmpFile.length() > maxSize) {
                tmpFile.delete();
                return new BaseState(false, AppInfo.MAX_SIZE);
            }
            State state = saveTmpFile(tmpFile, path, uploadDirPrefix);
            if (!state.isSuccess()) {
                tmpFile.delete();
            }
            return state;
        } catch (IOException e) {
            return new BaseState(false, AppInfo.IO_ERROR);
        }
    }

    private State saveTmpFile(File tmpFile, String path, String uploadDirPrefix) {
        State state = null;
        String fileName = getFileName(path);
        String key = uploadDirPrefix + fileName;
        byte[] buffer = null;
        try {
            FileInputStream fis = new FileInputStream(tmpFile);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            byte[] b = new byte[2048];
            int n;
            while ((n = fis.read(b)) != -1) {
                bos.write(b, 0, n);
            }
            fis.close();
            bos.close();
            buffer = bos.toByteArray();
            MultipartFile file = new InMemoryMultipartFile(fileName, buffer);
            FileKey fileKey = fileClient.upload(key, file);
            if (fileKey != null) {
                state = new BaseState(true);
                String[] keyArr = key.split("/");
                state.putInfo("name", keyArr[keyArr.length - 1]);
                state.putInfo("size", tmpFile.length());
                state.putInfo("title", key);
//                state.putInfo("url", getBaseUrl() + "/" + key);
                state.putInfo("url", key);
            } else {
                state = new BaseState(false, AppInfo.IO_ERROR);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            state = new BaseState(false, AppInfo.NOT_EXIST);
        } catch (Exception e) {
            e.printStackTrace();
            state = new BaseState(false, AppInfo.IO_ERROR);
        }
        return state;
    }

    private State getState(String[] files) {
        MultiState state = new MultiState(true);
        BaseState fileState = null;
        for (String url : files) {
            if (url == null) {
                break;
            }
            fileState = new BaseState(true);
//            fileState.putInfo("url", getBaseUrl() + "/" + url);
            fileState.putInfo("url", url);
            state.addState(fileState);
        }
        return state;
    }
}
